/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.lpcluster;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * Class representing a cluster of data points
 * @author edward
 */
public class Cluster {
	static int nCentroidPerf = 0;  // Pr

	// Cluster statistics
	private List<WebPage> points;
	private int nTotalWordCount;
	private WebPage centroid;
	
	/**
	 * Get Centroid of cluster
	 * @return 
	 */
	WebPage centroid() { return centroid; }
	
	/**
	 * Print list of points in cluster
	 */
	void print() { System.out.println(points); }
	
	/**
	 * Initialize cluster
	 */
	Cluster() {
		points = new ArrayList<WebPage>(10);
		nTotalWordCount = 0;
	}

	/**
	 * Assign a web page (data point) to the cluster
	 * @param point Point to add
	 */
	void addPoint(WebPage point) {
		points.add(point);
		nTotalWordCount += point.nTotalWordCount;

		// Only 1 point -> Set centroid
		if (points.size() == 1 && centroid == null) {
			centroid = point;
		}
	}
	
	/**
	 * Compute Group Centroid of this cluster
	 * @return true if centroid has changed since last computation; false otherwise
	 */
	boolean computeGroupCentroid() {	
	
		// Compute distance matrix and determine centroid
		int nSize = points.size();
		double nCentroidDistance = points.size();
		WebPage minPoint = null;
		List<List<Double>> distanceMatrix = new ArrayList<List<Double>>(nSize);

		// ***** LP-LOC # 4 *****
		int nNumLoops = nSize*(100-nCentroidPerf)/100;
		for (int i=0; i<nNumLoops; i++) {		
			List<Double> rowArray = new ArrayList<Double>(nSize);
			distanceMatrix.add(rowArray);

			// Compute distance with other points if current point is the centroid
			double nRowSum = 0;
			for (int j=0; j<nNumLoops; j++) {
				if (i == j) rowArray.add(0d); // No distance with itself
				else if (i < j) rowArray.add(points.get(i).distanceWith(points.get(j))); // Compute
				else rowArray.add(distanceMatrix.get(j).get(i));  // Distance is commutative

				nRowSum += rowArray.get(j);
			}
		
			// Update minimum point
			if (nRowSum < nCentroidDistance) {
				nCentroidDistance = nRowSum;
				minPoint = points.get(i);
			}
		}
	
		if (minPoint==null || centroid==minPoint) return false;  // No improvement in new centroid
	
		// Centroid changed
		centroid = minPoint;
		return true;
	}

	/**
	 * Compute Super Centroid of this cluster
	 * @return true if centroid has changed since last computation; false otherwise
	 */
	boolean computeSuperCentroid() {
		WebPage superCentroid = WebPage.webPageWithWebPages(points);

		WebPage existCentroid = centroid;
		double nExistSSE = computeSSE();

		centroid = superCentroid;
		double nNewSSE = computeSSE();

		if (nNewSSE < nExistSSE) return true;  // Use new centroid only if SSE is improved

		centroid = existCentroid;
		return false;
	}

	/**
	 * Compute Virtual Centroid of this cluster
	 * @return true if centroid has changed since last computation; false otherwise
	 */
	boolean computeVirtualCentroid() {
		List<WebPage> extractedPages = new ArrayList<WebPage>(points.size());
		Random random = new Random(LPCluster.getSeed());
		
		// ***** LP-LOC # 7b *****
		// Sample points in cluster to create Virtual Centroid
		for (WebPage point : points) {
			if (random.nextInt(100)<nCentroidPerf || point.nTotalWordCount==0) continue;
			extractedPages.add(point.initByCount(point.nTotalWordCount*point.nTotalWordCount/nTotalWordCount));
		}

		WebPage repCentroid = WebPage.webPageWithWebPages(extractedPages);

		WebPage existCentroid = centroid;
		double nExistSSE = computeSSE();

		centroid = repCentroid;
		double nNewSSE = computeSSE();

		if (nNewSSE < nExistSSE) return true;  // Use new centroid only if SSE is improved

		centroid = existCentroid;
		return false;
	}

	/**
	 * Computer SSE of cluster
	 * @return SSE of cluster
	 */
	double computeSSE() {
		double nSSE = 0;
		
		// ***** LP-LOC # 5b *****
		// Loop through points to compute SSE
		for (WebPage point : points)
			if (point != centroid) nSSE += Math.pow(point.distanceWith(centroid),2);
		
		return nSSE;
	}
	
	/**
	 * Remove all points in cluster keeping the centroid
	 */
	void removeAllExceptCentroid() {
		
		// ***** LP-LOC # 8a *****
		while (points.size()>0 && !(points.size()==1 && points.get(0)==centroid))
			if (points.get(0) == centroid) points.remove(1);
			else points.remove(0);

		nTotalWordCount = (points.isEmpty()?0:points.get(0).nTotalWordCount);
	}
}
